/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include "drv_ospi.h"
#include "drivers/dev_spi.h"
#include "drv_config.h"

#ifdef RT_USING_OSPI

// #define DBG_LVL              DBG_INFO
// #define LOG_TAG              "drv.ospi"
#include <rtdbg.h>


#ifndef RT_MIN
#define RT_MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#if defined(BSP_USING_OSPI)

// struct stm32_ospi_bus
// {
//     // OSPI_HandleTypeDef OSPI_Handler;
//     char *bus_name;
// };

struct rt_spi_bus _ospi_bus1; /*这个是必须的, 用来注册总线的*/
// struct stm32_ospi_bus _stm32_ospi_bus;  /*@tips: 这个结构体, 本来是用来封装私有数据的. 在本文中无用. 我用hospi1, cpt代替了.*/

extern OSPI_HandleTypeDef hospi1;
#ifdef BSP_OSPI_USING_DMA
struct rt_completion cpt;  // 添加完成量用于DMA传输同步
#endif 

static void ospi_send_cmd(struct rt_qspi_message *message)
{
    RT_ASSERT(message != RT_NULL);

    OSPI_RegularCmdTypeDef Cmdhandler = {0};

    /* set OSPI cmd struct */
    Cmdhandler.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
    Cmdhandler.FlashId = HAL_OSPI_FLASH_ID_1;
    Cmdhandler.Instruction = message->instruction.content;
    Cmdhandler.Address = message->address.content;
    Cmdhandler.DummyCycles = message->dummy_cycles;
    
    /* Instruction mode */
    if (message->instruction.qspi_lines == 0)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_NONE;
    else if (message->instruction.qspi_lines == 1)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
    else if (message->instruction.qspi_lines == 2)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_2_LINES;
    else if (message->instruction.qspi_lines == 4)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_4_LINES;

    /* Address mode */
    if (message->address.qspi_lines == 0)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_NONE;
    else if (message->address.qspi_lines == 1)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_1_LINE;
    else if (message->address.qspi_lines == 2)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_2_LINES;
    else if (message->address.qspi_lines == 4)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_4_LINES;

    /* Address size */
    if (message->address.size == 24)
        Cmdhandler.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
    else
        Cmdhandler.AddressSize = HAL_OSPI_ADDRESS_32_BITS;

    /* Data mode */
    if (message->qspi_data_lines == 0)
        Cmdhandler.DataMode = HAL_OSPI_DATA_NONE;
    else if (message->qspi_data_lines == 1)
        Cmdhandler.DataMode = HAL_OSPI_DATA_1_LINE;
    else if (message->qspi_data_lines == 2)
        Cmdhandler.DataMode = HAL_OSPI_DATA_2_LINES;
    else if (message->qspi_data_lines == 4)
        Cmdhandler.DataMode = HAL_OSPI_DATA_4_LINES;

    Cmdhandler.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
    Cmdhandler.NbData = message->parent.length;
    
    HAL_OSPI_Command(&hospi1, &Cmdhandler, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
}


void write_cs(struct rt_spi_device *device, struct rt_spi_message *message, int value)
{
#ifdef BSP_OSPI_USING_SOFTCS
    if (message->cs_take && (device->cs_pin != PIN_NONE))
    {
        rt_pin_write(device->cs_pin, value);
    }
#endif
}


/*@tips:总结：
1. 分类要清楚.因为我们使用ospi dma, 主要是针对 qspi_lcd发送图像的情况, 这是length一定很大, 并且是tx模式
2. 基本上没有使用ospi_bus, 全部直接使用的是hospi1, 这个是cubeMx生成的. 简单方便, 容易移植
3. 对于很大的length, 要分包.发生这种情况, 一定是qspi lcd发送图像. 对于分包, 每个send_cmd和send_data要一起发送, 不能只发一个send_cmd, 和很多个send_data,
   否则会出异常. 这是qspi的特殊性. 在这个地方我没有注意到, 一个send_cmd后面跟了多个send_data, 发送异常, 折腾了2天. 终于搞懂原因了.
4. 每个分包, 都要被cs=0和cs=1包裹. 为了解决这个问题, 我重新改写了ospixfer的cs控制.
*/
static rt_ssize_t ospixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    rt_ssize_t result = 0;
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);

    struct rt_qspi_message *ospi_message = (struct rt_qspi_message *)message;

    const rt_uint8_t *sndb = message->send_buf;
    rt_uint8_t *rcvb = message->recv_buf;
    rt_int32_t length = message->length;

    /*情况1*/
    if(!(sndb && length >= 100)) { /*只有tx且数量大于100, 才使用dma。其余情况都使用非dma。*/
        write_cs(device, message, PIN_LOW);
        ospi_send_cmd(ospi_message);
        /* send data */
        if (sndb)
        {
            if (length != 0)
            {
                if (HAL_OSPI_Transmit(&hospi1, (rt_uint8_t *)sndb, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) == HAL_OK)
                {
                    result = length;
                }
                else
                {
                    LOG_E("OSPI send data failed(%d)!", hospi1.ErrorCode);
                    hospi1.State = HAL_OSPI_STATE_READY;
                    result = -RT_ERROR;
                }
            }
            else
            {
                result = 1;
            }
        }
        else if (rcvb) /* recv data */
        {
            if (HAL_OSPI_Receive(&hospi1, rcvb, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) == HAL_OK)
            {
                result = length;
            }
            else
            {
                LOG_E("OSPI recv data failed(%d)!", hospi1.ErrorCode);
                hospi1.State = HAL_OSPI_STATE_READY;
                result = -RT_ERROR;
            }
        }
        write_cs(device, message, PIN_HIGH);
        goto _exit;        
    }

  
    /*执行到这里的时候，肯定是tx且数量>=100个, 需要使用dma*/
#ifndef BSP_OSPI_USING_DMA
    /*情况2*/
    write_cs(device, message, PIN_LOW);
    ospi_send_cmd(ospi_message);
    if (HAL_OSPI_Transmit(&hospi1, (rt_uint8_t *)sndb, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) == HAL_OK)
    {
        result = length;
    }
    else
    {
        LOG_E("OSPI send data failed(%d)!", hospi1.ErrorCode);
        hospi1.State = HAL_OSPI_STATE_READY;
        result = -RT_ERROR;
    }
    write_cs(device, message, PIN_HIGH);
    goto _exit;
#else
    /*情况3*/
    rt_size_t remaining = message->length;
    rt_uint8_t *ptr = (rt_uint8_t *)sndb;  
    rt_uint8_t is_first_trans=1; /*分包处理*/

    while (remaining > 0)
    {
        rt_size_t chunk = RT_MIN(remaining,16384); /*用65536显示不正常,可能是rt_malloc_align出问题了*/
        
        /* Handle data alignment and DMA buffer preparation */
        rt_uint32_t* dma_aligned_buffer = RT_NULL;
        rt_uint8_t* p_txrx_buffer = RT_NULL;
        
    #if defined(SOC_SERIES_STM32H7)
        if (!RT_IS_ALIGN((rt_uint32_t)ptr, 32))
        {
            dma_aligned_buffer = (rt_uint32_t *)rt_malloc_align(chunk, 32);
            if (sndb) rt_memcpy(dma_aligned_buffer, ptr, chunk);
            p_txrx_buffer = (rt_uint8_t *)dma_aligned_buffer;
        }
        else
        {
            p_txrx_buffer = ptr;
        }
    #else
        p_txrx_buffer = ptr;
    #endif

        if(is_first_trans) {
            ospi_message->address.content=ospi_message->address.content;
            is_first_trans=0;
        }
        else {
            /*如果执行到这里,一定是qspi lcd发送图像数据的分包处理.
              因为对于qspi flash的dma, 写是以page为单位(256bytes), 读通常以sector为单位(4096bytes). 所以不会到这里来.
            */
            ospi_message->address.content=0x3c<<8; 
        }
        ospi_message->parent.length = chunk;

        /*send data using dma*/
        write_cs(device, message, PIN_LOW);  /*分包处理, send_cmd与send_data要配对使用, 否则就Error了。这个问题折腾了我2天,没有注意到。*/
        ospi_send_cmd(ospi_message);
        SCB_CleanDCache_by_Addr((uint32_t *)p_txrx_buffer, chunk);
        if (HAL_OSPI_Transmit_DMA(&hospi1, p_txrx_buffer) == HAL_OK)
        {
            LOG_I("run dma!\n");
            if (rt_completion_wait(&cpt, RT_WAITING_FOREVER) != RT_EOK)
            {
                result = -RT_ETIMEOUT;
                break;
            }
            // while (hospi1.XferCount != 0);
        }
        else{
            LOG_E("OSPI DMA send data failed(%d)!", hospi1.ErrorCode);
            hospi1.State = HAL_OSPI_STATE_READY;
            result = -RT_ERROR;
        }
        write_cs(device, message, PIN_HIGH);

        if (dma_aligned_buffer)
        {
#if defined(SOC_SERIES_STM32H7)
            rt_free_align(dma_aligned_buffer);
#endif
        }

        ptr += chunk;
        remaining -= chunk;
        result += chunk;
    }
#endif


_exit:    
    return result;
}

/*@tips: copy from CubeMx generated main.c*/
static void MX_OCTOSPI1_Init(void)
{

  /* USER CODE BEGIN OCTOSPI1_Init 0 */

  /* USER CODE END OCTOSPI1_Init 0 */

  OSPIM_CfgTypeDef sOspiManagerCfg = {0};

  /* USER CODE BEGIN OCTOSPI1_Init 1 */

  /* USER CODE END OCTOSPI1_Init 1 */
  /* OCTOSPI1 parameter configuration*/
  hospi1.Instance = OCTOSPI1;
  hospi1.Init.FifoThreshold = 1;
  hospi1.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE;
  hospi1.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON;
  hospi1.Init.DeviceSize = 23;
  hospi1.Init.ChipSelectHighTime = 1;
  hospi1.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE;
  hospi1.Init.ClockMode = HAL_OSPI_CLOCK_MODE_0;
  hospi1.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED;
  hospi1.Init.ClockPrescaler = 4;
  hospi1.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hospi1.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE;
  hospi1.Init.ChipSelectBoundary = 0;
  hospi1.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED;
  hospi1.Init.MaxTran = 0;
  hospi1.Init.Refresh = 0;
  if (HAL_OSPI_Init(&hospi1) != HAL_OK)
  {
    Error_Handler();
  }
  sOspiManagerCfg.ClkPort = 1;
  sOspiManagerCfg.NCSPort = 1;
  sOspiManagerCfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW;
  if (HAL_OSPIM_Config(&hospi1, &sOspiManagerCfg, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN OCTOSPI1_Init 2 */

  /* USER CODE END OCTOSPI1_Init 2 */

}

static rt_err_t ospi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{

    MX_OCTOSPI1_Init();
    
    return RT_EOK;
}

static const struct rt_spi_ops stm32_ospi_ops =
{
    .configure = ospi_configure,
    .xfer = ospixfer,
};

static int stm32_ospi_register_bus(const char *name)
{
    RT_ASSERT(name != RT_NULL);

    return rt_qspi_bus_register(&_ospi_bus1, name, &stm32_ospi_ops);
}

rt_err_t rt_hw_ospi_device_attach(const char *bus_name, const char *device_name, rt_base_t cs_pin, rt_uint8_t data_line_width, void (*enter_qspi_mode)(), void (*exit_qspi_mode)())
{
    struct rt_qspi_device *ospi_device = RT_NULL;
    rt_err_t result = RT_EOK;

    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);
    RT_ASSERT(data_line_width == 1 || data_line_width == 2 || data_line_width == 4);

    ospi_device = (struct rt_qspi_device *)rt_malloc(sizeof(struct rt_qspi_device));
    if (ospi_device == RT_NULL)
    {
        LOG_E("no memory, ospi bus attach device failed!");
        return -RT_ENOMEM;
    }

    ospi_device->config.qspi_dl_width = data_line_width;
    ospi_device->enter_qspi_mode = enter_qspi_mode;
    ospi_device->exit_qspi_mode = exit_qspi_mode;

#ifdef BSP_OSPI_USING_SOFTCS
    result = rt_spi_bus_attach_device_cspin(&ospi_device->parent, device_name, bus_name, cs_pin, RT_NULL);
#else
    result = rt_spi_bus_attach_device_cspin(&ospi_device->parent, device_name, bus_name, PIN_NONE, RT_NULL);
#endif /* BSP_OSPI_USING_SOFTCS */

    if (result != RT_EOK && ospi_device)
    {
        rt_free(ospi_device);
    }

    return result;
}

static int rt_hw_ospi_bus_init(void)
{
#ifdef BSP_OSPI_USING_DMA
    rt_completion_init(&cpt);
#endif    

    ospi_configure(NULL, NULL);  /*@tips:Add by xujc, for bug at dev_qspi_core.c:48. Force Init()*/
    return stm32_ospi_register_bus("ospi1");
}
INIT_BOARD_EXPORT(rt_hw_ospi_bus_init);

#endif /* BSP_USING_OSPI */
#endif /* RT_USING_OSPI */

#ifdef BSP_OSPI_USING_DMA
void OCTOSPI1_IRQHandler(void)
{
    rt_interrupt_enter();

    /*@tips: 只有HAL_OSPI_IRQHandler函数才会调用HAL_OSPI_TxCpltCallback和HAL_OSPI_RxCpltCallback
     因此，要使用Callback，必须开启OCTOSPI1_IRQn中断*/
    LOG_I("ospi_irq\n");
    HAL_OSPI_IRQHandler(&hospi1);

    rt_interrupt_leave();
}

void MDMA_IRQHandler(void)
{
    rt_interrupt_enter();

    LOG_I("mdma_irq\n");
    HAL_MDMA_IRQHandler(hospi1.hmdma);

    rt_interrupt_leave();
}
void HAL_OSPI_TxCpltCallback(OSPI_HandleTypeDef *hospi)
{
    LOG_I("tx_cplt\n");
    rt_completion_done(&cpt);
}

#endif /* BSP_OSPI_USING_DMA */